/*
 * src/interfaces/libpq/win32.c
 *
 *
 *    FILE
 *        win32.c
 *
 *    DESCRIPTION
 *        Win32 support functions.
 *
 * Contains table and functions for looking up win32 socket error
 * descriptions. But will/may contain other win32 helper functions
 * for libpq.
 *
 * The error constants are taken from the Frambak Bakfram LGSOCKET
 * library guys who in turn took them from the Winsock FAQ.
 *
 * Portions Copyright (c) 1996-2017, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 */

/* Make stuff compile faster by excluding not used stuff */

#define VC_EXTRALEAN
#ifndef __MINGW32__
#define NOGDI
#endif
#define NOCRYPT

#include "postgres_fe.h"

#include "win32.h"

/* Declared here to avoid pulling in all includes, which causes name collisions */
#ifdef ENABLE_NLS
extern char *libpq_gettext(const char *msgid) pg_attribute_format_arg(1);
#else
#define libpq_gettext(x) (x)
#endif


static struct WSErrorEntry
{
    DWORD        error;
    const char *description;
}            WSErrors[] =

{
    {
        0, "No error"
    },
    {
        WSAEINTR, "Interrupted system call"
    },
    {
        WSAEBADF, "Bad file number"
    },
    {
        WSAEACCES, "Permission denied"
    },
    {
        WSAEFAULT, "Bad address"
    },
    {
        WSAEINVAL, "Invalid argument"
    },
    {
        WSAEMFILE, "Too many open sockets"
    },
    {
        WSAEWOULDBLOCK, "Operation would block"
    },
    {
        WSAEINPROGRESS, "Operation now in progress"
    },
    {
        WSAEALREADY, "Operation already in progress"
    },
    {
        WSAENOTSOCK, "Socket operation on non-socket"
    },
    {
        WSAEDESTADDRREQ, "Destination address required"
    },
    {
        WSAEMSGSIZE, "Message too long"
    },
    {
        WSAEPROTOTYPE, "Protocol wrong type for socket"
    },
    {
        WSAENOPROTOOPT, "Bad protocol option"
    },
    {
        WSAEPROTONOSUPPORT, "Protocol not supported"
    },
    {
        WSAESOCKTNOSUPPORT, "Socket type not supported"
    },
    {
        WSAEOPNOTSUPP, "Operation not supported on socket"
    },
    {
        WSAEPFNOSUPPORT, "Protocol family not supported"
    },
    {
        WSAEAFNOSUPPORT, "Address family not supported"
    },
    {
        WSAEADDRINUSE, "Address already in use"
    },
    {
        WSAEADDRNOTAVAIL, "Cannot assign requested address"
    },
    {
        WSAENETDOWN, "Network is down"
    },
    {
        WSAENETUNREACH, "Network is unreachable"
    },
    {
        WSAENETRESET, "Net connection reset"
    },
    {
        WSAECONNABORTED, "Software caused connection abort"
    },
    {
        WSAECONNRESET, "Connection reset by peer"
    },
    {
        WSAENOBUFS, "No buffer space available"
    },
    {
        WSAEISCONN, "Socket is already connected"
    },
    {
        WSAENOTCONN, "Socket is not connected"
    },
    {
        WSAESHUTDOWN, "Cannot send after socket shutdown"
    },
    {
        WSAETOOMANYREFS, "Too many references, cannot splice"
    },
    {
        WSAETIMEDOUT, "Connection timed out"
    },
    {
        WSAECONNREFUSED, "Connection refused"
    },
    {
        WSAELOOP, "Too many levels of symbolic links"
    },
    {
        WSAENAMETOOLONG, "File name too long"
    },
    {
        WSAEHOSTDOWN, "Host is down"
    },
    {
        WSAEHOSTUNREACH, "No route to host"
    },
    {
        WSAENOTEMPTY, "Directory not empty"
    },
    {
        WSAEPROCLIM, "Too many processes"
    },
    {
        WSAEUSERS, "Too many users"
    },
    {
        WSAEDQUOT, "Disc quota exceeded"
    },
    {
        WSAESTALE, "Stale NFS file handle"
    },
    {
        WSAEREMOTE, "Too many levels of remote in path"
    },
    {
        WSASYSNOTREADY, "Network system is unavailable"
    },
    {
        WSAVERNOTSUPPORTED, "Winsock version out of range"
    },
    {
        WSANOTINITIALISED, "WSAStartup not yet called"
    },
    {
        WSAEDISCON, "Graceful shutdown in progress"
    },
    {
        WSAHOST_NOT_FOUND, "Host not found"
    },
    {
        WSATRY_AGAIN, "NA Host not found / SERVFAIL"
    },
    {
        WSANO_RECOVERY, "Non recoverable FORMERR||REFUSED||NOTIMP"
    },
    {
        WSANO_DATA, "No host data of that type was found"
    },
    {
        0, 0
    }                            /* End of table */
};


/*
 * Returns 0 if not found, linear but who cares, at this moment
 * we're already in pain :)
 */

static int
LookupWSErrorMessage(DWORD err, char *dest)
{
    struct WSErrorEntry *e;

    for (e = WSErrors; e->description; e++)
    {
        if (e->error == err)
        {
            strcpy(dest, e->description);
            return 1;
        }
    }
    return 0;
}


struct MessageDLL
{
    const char *dll_name;
    void       *handle;
    int            loaded;            /* BOOL */
}            dlls[] =

{
    {
        "netmsg.dll", 0, 0
    },
    {
        "winsock.dll", 0, 0
    },
    {
        "ws2_32.dll", 0, 0
    },
    {
        "wsock32n.dll", 0, 0
    },
    {
        "mswsock.dll", 0, 0
    },
    {
        "ws2help.dll", 0, 0
    },
    {
        "ws2thk.dll", 0, 0
    },
    {
        0, 0, 1
    }                            /* Last one, no dll, always loaded */
};

#define DLLS_SIZE (sizeof(dlls)/sizeof(struct MessageDLL))

/*
 * Returns a description of the socket error by first trying
 * to find it in the lookup table, and if that fails, tries
 * to load any of the winsock dlls to find that message.
 * The DLL thing works from Nt4 (spX ?) up, but some special
 * versions of winsock might have this as well (seen on Win98 SE
 * special install)               / Magnus Naeslund (mag@fbab.net)
 *
 */

const char *
winsock_strerror(int err, char *strerrbuf, size_t buflen)
{// #lizard forgives
    unsigned long flags;
    int            offs,
                i;
    int            success = LookupWSErrorMessage(err, strerrbuf);

    for (i = 0; !success && i < DLLS_SIZE; i++)
    {

        if (!dlls[i].loaded)
        {
            dlls[i].loaded = 1; /* Only load once */
            dlls[i].handle = (void *) LoadLibraryEx(
                                                    dlls[i].dll_name,
                                                    0,
                                                    LOAD_LIBRARY_AS_DATAFILE);
        }

        if (dlls[i].dll_name && !dlls[i].handle)
            continue;            /* Didn't load */

        flags = FORMAT_MESSAGE_FROM_SYSTEM
            | FORMAT_MESSAGE_IGNORE_INSERTS
            | (dlls[i].handle ? FORMAT_MESSAGE_FROM_HMODULE : 0);

        success = 0 != FormatMessage(
                                     flags,
                                     dlls[i].handle, err,
                                     MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT),
                                     strerrbuf, buflen - 64,
                                     0
            );
    }

    if (!success)
        sprintf(strerrbuf, libpq_gettext("unrecognized socket error: 0x%08X/%d"), err, err);
    else
    {
        strerrbuf[buflen - 1] = '\0';
        offs = strlen(strerrbuf);
        if (offs > (int) buflen - 64)
            offs = buflen - 64;
        sprintf(strerrbuf + offs, " (0x%08X/%d)", err, err);
    }
    return strerrbuf;
}
